#include "modelviewwidget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QMouseEvent>
#include <QOpenGLContext>

ModelViewWidget::ModelViewWidget(QString path, QWidget *parent)
    : QOpenGLWidget(parent)
    ,clearColor(Qt::white)
    ,m_indexBuffer(QOpenGLBuffer::IndexBuffer)
    ,m_pitch(0.0f)
    ,m_roll(0.0f)
    ,m_yaw(0.0f)
{
    QSurfaceFormat requestedFormat;
    requestedFormat.setDepthBufferSize( 24 );
    requestedFormat.setMajorVersion( 3 );
    requestedFormat.setMinorVersion( 3 );
    requestedFormat.setSamples( 4 );
    requestedFormat.setProfile( QSurfaceFormat::CoreProfile );
    QSurfaceFormat::setDefaultFormat( requestedFormat );

    m_filepath = path;
    camerax = 2.0f;
    cameray = 2.0f;
    cameraz = 2.0f;
    x = 1.0f;
    y = 1.0f;
    z = 1.0f;
    m_error = false;
//    this->setMinimumHeight(400);
//    this->setMinimumWidth(400);
}

ModelViewWidget::~ModelViewWidget()
{
    disconnect(this, 0, 0, 0);
    makeCurrent();
    m_shaderProgram.deleteLater();
    m_shaderProgramCoordinatePlanes.deleteLater();
    m_vao.destroy();
    m_vaoCoordinatePlanes.destroy();
    m_vertexBuffer.destroy();
    m_normalBuffer.destroy();
    m_textureUVBuffer.destroy();
    m_indexBuffer.destroy();
    m_coordinatePlanesBuffer.destroy();
    delete pointsCoordinatePlanes;
    doneCurrent();
}

QSize ModelViewWidget::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize ModelViewWidget::sizeHint() const
{
    return QSize(200, 200);
}

void ModelViewWidget::initializeGL()
{
    initializeOpenGLFunctions();
    makeCurrent();
    glEnable(GL_DEPTH_TEST);

    createShaderProgram();
    createBuffers();
    createAttributes();
    setupLightingAndMatrices();

#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "File:" <<  __FILE__ << " Line:"<<  __LINE__ << " Func:" <<  __FUNCTION__;
#endif
}

void ModelViewWidget::paintGL()
{
    glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), clearColor.alphaF());
    // Clear color and depth buffers
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if(m_error)
        return;

    m_view.setToIdentity();
    m_view.lookAt(
                QVector3D(camerax, cameray, cameraz),    // Camera Position
                QVector3D(0.0f, 0.0f, 0.0f),    // Point camera looks towards
                QVector3D(0.0f, 1.0f, 0.0f));   // Up vector

    // Bind shader program
    m_shaderProgram.bind();

    // Set the model matrix
    // Translate and rotate it a bit to get a better view of the model
    m_model.setToIdentity();
    m_model.translate(x, y, z);
    m_model.rotate(m_pitch, 1.0f, 0.0f, 0.0f);
    m_model.rotate(m_roll, 0.0f, 0.0f, 1.0f);
    m_model.rotate(m_yaw, 0.0f, 1.0f, 0.0f);

    // Set shader uniforms for light information
    m_shaderProgram.setUniformValue( "lightPosition", m_lightInfo.Position );
    m_shaderProgram.setUniformValue( "lightIntensity", m_lightInfo.Intensity );

    // Bind VAO and draw model
    m_vao.bind();
    drawNode(m_rootNode.data(), QMatrix4x4());
    m_vao.release();
    m_shaderProgram.release();

    // 绘制网格线
    m_shaderProgramCoordinatePlanes.bind();
    m_shaderProgramCoordinatePlanes.setUniformValue( "lightPosition", m_lightInfo.Position );
    m_shaderProgramCoordinatePlanes.setUniformValue( "lightIntensity", m_lightInfo.Intensity );

    QMatrix4x4 modelViewMatrix = m_view ;
    QMatrix3x3 normalMatrix = modelViewMatrix.normalMatrix();
    QMatrix4x4 mvp = m_projection * modelViewMatrix;

    m_shaderProgramCoordinatePlanes.setUniformValue( "MV", modelViewMatrix );// Transforming to eye space
    m_shaderProgramCoordinatePlanes.setUniformValue( "N", normalMatrix );    // Transform normal to Eye space
    m_shaderProgramCoordinatePlanes.setUniformValue( "MVP", mvp );           // Matrix for transforming to Clip space

    m_vaoCoordinatePlanes.bind();
    glDrawArrays(GL_LINES, 0, pointsNumCoordinatePlanes);
    m_vaoCoordinatePlanes.release();
}

void ModelViewWidget::resizeGL(int width, int height)
{
    glViewport( 0, 0, width, height );
    m_projection.setToIdentity();
    m_projection.perspective(60.0f, (float)width/height, .3f, 1000);
}

void ModelViewWidget::mousePressEvent(QMouseEvent *event)
{
    lastPosMouse = event->pos();
}

void ModelViewWidget::mouseMoveEvent(QMouseEvent *event)
{
    int dx = event->x() - lastPosMouse.x();
    int dy = event->y() - lastPosMouse.y();

    if (event->buttons() & Qt::LeftButton) {
        cameray += (float) dy/2.0f /100.0f;
        camerax -= (float) dx/2.0f /100.0f;
        update();
    } else if (event->buttons() & Qt::RightButton) {
        cameray += (float) dy/2.0f /100.0f;
        cameraz += (float) dx/2.0f /100.0f;
        update();
    }

#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "camerax:" << camerax << "cameray:" << cameray << "cameraz:" << cameraz;
#endif

    lastPosMouse = event->pos();
}

void ModelViewWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
    emit clicked();
}

void ModelViewWidget::wheelEvent(QWheelEvent *event)
{
    if(event->delta() < 0){
        camerax *= 1.2f;
        cameray *= 1.2f;
        cameraz *= 1.2f;
        update();
    }else {
        camerax /= 1.2f;
        cameray /= 1.2f;
        cameraz /= 1.2f;
        update();
    }
    emit clicked();
#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "wheelEvent delta:" << event->delta();
#endif
}

// 设置模型的姿态角
void ModelViewWidget::setModelAttitude(float pitch, float roll, float yaw)
{
    m_pitch = pitch;
    m_roll = roll;
    m_yaw = yaw;
    update();
}

// 生成绘制坐标平面使用的点
int ModelViewWidget::createCoordinatePlanesPoints(float length, int lineNum)
{
    pointsNumCoordinatePlanes = (lineNum + 1) * 2 * 3 * 2;
    pointsCoordinatePlanes = new float[pointsNumCoordinatePlanes * 3]();
    int cnt_float = 0;

    for(int i = 0; i < lineNum + 1; i++){
        // x-o-y parallel to x, y increase and x same
        // start point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;
        // end point
        pointsCoordinatePlanes[cnt_float++] = length;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;

        // x-o-y parallel to y,  x increase and y same
        // start point
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = 0;
        // end point
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = length;
        pointsCoordinatePlanes[cnt_float++] = 0;

        // y-o-z parallel to y,  z increase and y same
        // start point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        // end point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;

        // y-o-z parallel to z, y increase and z same
        // start point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;
        // end point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = length;

        // x-o-z parallel to z, x increase and z same
        // start point
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = 0;
        // end point
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length;

        // x-o-z parallel to x,  z increase and x same
        // start point
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
        // end point
        pointsCoordinatePlanes[cnt_float++] = length;
        pointsCoordinatePlanes[cnt_float++] = 0;
        pointsCoordinatePlanes[cnt_float++] = length * i / lineNum;
    }
    return 0;
}

// 读取模型，生成buffer
void ModelViewWidget::createBuffers()
{
    ModelLoader model;

#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "Now load model:" << m_filepath;
#endif

    if(!model.Load(m_filepath))
    {
        LOGE("!!!!!!error when load model:%s", m_filepath.toStdString().data());
        qDebug() << "!!!!!!error when load model:" << m_filepath;
        m_error = true;
        return;
    }

    QVector<float> *vertices;
    QVector<float> *normals;
    QVector<QVector<float> > *textureUV;
    QVector<unsigned int> *indices;

    model.getBufferData(&vertices, &normals, &indices);
    model.getTextureData(&textureUV, 0, 0);

    // Create a vertex array object
    m_vao.create();
    m_vao.bind();

    // Create a buffer and copy the vertex data to it
    m_vertexBuffer.create();
    m_vertexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_vertexBuffer.bind();
    m_vertexBuffer.allocate( &(*vertices)[0], vertices->size() * sizeof( float ) );
#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "size of m_vertexBuffer:" << m_vertexBuffer.size();// 41112
#endif

    // Create a buffer and copy the vertex data to it
    m_normalBuffer.create();
    m_normalBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_normalBuffer.bind();
    m_normalBuffer.allocate( &(*normals)[0], normals->size() * sizeof( float ) );
#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "size of m_normalBuffer:" << m_normalBuffer.size();// 41112
#endif

    if(textureUV != 0 && textureUV->size() != 0)
    {
        // Create a buffer and copy the vertex data to it
        m_textureUVBuffer.create();
        m_textureUVBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
        m_textureUVBuffer.bind();
        int texSize = 0;
        for(int ii=0; ii<textureUV->size(); ++ii)
            texSize += textureUV->at(ii).size();

        m_textureUVBuffer.allocate( &(*textureUV)[0][0], texSize * sizeof( float ) );
#if DEBUG_MODEL_VIEW_WIDGET
        qDebug() << "size of m_textureUVBuffer:" << m_textureUVBuffer.size();// null
#endif
    }

    // Create a buffer and copy the index data to it
    m_indexBuffer.create();
    m_indexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_indexBuffer.bind();
    m_indexBuffer.allocate( &(*indices)[0], indices->size() * sizeof( unsigned int ) );
#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "size of m_indexBuffer:" << m_indexBuffer.size();// 85272
#endif

    m_rootNode = model.getNodeData();

    m_vaoCoordinatePlanes.create();
    m_vaoCoordinatePlanes.bind();
    m_coordinatePlanesBuffer.create();
    m_coordinatePlanesBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    m_coordinatePlanesBuffer.bind();
    // 生成网格线顶点坐标
    createCoordinatePlanesPoints(1.5f, 10);
    m_coordinatePlanesBuffer.allocate( pointsCoordinatePlanes,
                                       pointsNumCoordinatePlanes * 3 * sizeof(float) );
#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "size of m_coordinatePlanesBuffer:" << m_coordinatePlanesBuffer.size();// 1584
#endif
}

// buffer的映射
void ModelViewWidget::createAttributes()
{
    if(m_error){
        LOGE("m_error, return now!");
        qDebug() << " Func:" <<  __FUNCTION__ << "m_error, return now!";
        return;
    }

    m_vao.bind();
    // Set up the vertex array state
    m_shaderProgram.bind();

    // Map vertex data to the vertex shader's layout location '0'
    m_vertexBuffer.bind();
    m_shaderProgram.enableAttributeArray( 0 );      // layout location
    m_shaderProgram.setAttributeBuffer( 0,          // layout location
                                        GL_FLOAT,   // data's type
                                        0,          // Offset to data in buffer
                                        3);         // number of components (3 for x,y,z)

    // Map normal data to the vertex shader's layout location '1'
    m_normalBuffer.bind();
    m_shaderProgram.enableAttributeArray( 1 );      // layout location
    m_shaderProgram.setAttributeBuffer( 1,          // layout location
                                        GL_FLOAT,   // data's type
                                        0,          // Offset to data in buffer
                                        3);         // number of components (3 for x,y,z)

    if(m_textureUVBuffer.isCreated())
    {
        m_textureUVBuffer.bind();
        m_shaderProgram.enableAttributeArray( 2 );      // layout location
        m_shaderProgram.setAttributeBuffer( 2,          // layout location
                                            GL_FLOAT,   // data's type
                                            0,          // Offset to data in buffer
                                            2);         // number of components (2 for u,v)
    }

    m_vaoCoordinatePlanes.bind();
    // Set up the vertex array state
    m_shaderProgramCoordinatePlanes.bind();

    // Map vertex data to the vertex shader's layout location '0'
    m_coordinatePlanesBuffer.bind();
    m_shaderProgramCoordinatePlanes.enableAttributeArray( 0 );      // layout location
    m_shaderProgramCoordinatePlanes.setAttributeBuffer( 0,          // layout location
                                        GL_FLOAT,   // data's type
                                        0,          // Offset to data in buffer
                                        3);         // number of components (3 for x,y,z)

#if DEBUG_MODEL_VIEW_WIDGET
    qDebug() << "File:" <<  __FILE__ << " Line:"<<  __LINE__ << " Func:" <<  __FUNCTION__;
    qDebug() << "m_vao isCreated:" << m_vao.isCreated();
    qDebug() << "m_vaoCoordinatePlanes isCreated:" << m_vaoCoordinatePlanes.isCreated();
#endif
}

void ModelViewWidget::setupLightingAndMatrices()
{
    m_view.setToIdentity();
    m_view.lookAt(
                QVector3D(camerax, cameray, cameraz),    // Camera Position
                QVector3D(0.0f, 0.0f, 0.0f),    // Point camera looks towards
                QVector3D(0.0f, 1.0f, 0.0f));   // Up vector

    float aspect = 4.0f/3.0f;
    m_projection.setToIdentity();
    m_projection.perspective(
                60.0f,          // field of vision
                aspect,         // aspect ratio
                0.3f,           // near clipping plane
                1000.0f);       // far clipping plane

    m_lightInfo.Position = QVector4D( -1.0f, 1.0f, 1.0f, 1.0f );
    //m_lightInfo.Intensity = QVector3D( .5f, .5f, .f5);
    m_lightInfo.Intensity = QVector3D( 1.0f, 1.0f, 1.0f);

    m_materialInfo.Ambient = QVector3D( 0.1f, 0.05f, 0.0f );
    m_materialInfo.Diffuse = QVector3D( .9f, .6f, .2f );
    m_materialInfo.Specular = QVector3D( .2f, .2f, .2f );
    m_materialInfo.Shininess = 50.0f;
}

void ModelViewWidget::drawNode(const Node *node, QMatrix4x4 objectMatrix)
{
    // Prepare matrices
    objectMatrix *= node->transformation;
    QMatrix4x4 modelMatrix = m_model * objectMatrix;
    QMatrix4x4 modelViewMatrix = m_view * modelMatrix;
    QMatrix3x3 normalMatrix = modelViewMatrix.normalMatrix();
    QMatrix4x4 mvp = m_projection * modelViewMatrix;

    m_shaderProgram.setUniformValue( "MV", modelViewMatrix );// Transforming to eye space
    m_shaderProgram.setUniformValue( "N", normalMatrix );    // Transform normal to Eye space
    m_shaderProgram.setUniformValue( "MVP", mvp );           // Matrix for transforming to Clip space

    // Draw each mesh in this node
    for(int imm = 0; imm<node->meshes.size(); ++imm)
    {
        if(node->meshes[imm]->material->Name == QString("DefaultMaterial"))
            setMaterialUniforms(m_materialInfo);
        else
            setMaterialUniforms(*node->meshes[imm]->material);

        glDrawElements( GL_TRIANGLES, node->meshes[imm]->indexCount, GL_UNSIGNED_INT
                            , (const void*)(node->meshes[imm]->indexOffset * sizeof(unsigned int)) );
    }

    // Recursively draw this nodes children nodes
    for(int inn = 0; inn<node->nodes.size(); ++inn)
        drawNode(&node->nodes[inn], objectMatrix);
}

void ModelViewWidget::setMaterialUniforms(MaterialInfo &mater)
{
    m_shaderProgram.setUniformValue( "Ka", mater.Ambient );
    m_shaderProgram.setUniformValue( "Kd", mater.Diffuse );
    m_shaderProgram.setUniformValue( "Ks", mater.Specular );
    m_shaderProgram.setUniformValue( "shininess", mater.Shininess );
}

const char *vsrc1 =
"#version 330 core\n"
"layout (location = 0) in vec3 vertexPosition;\n"
"layout (location = 1) in vec3 vertexNormal;\n"
"uniform mat4 MV;\n"
"uniform mat3 N;\n"
"uniform mat4 MVP;\n"
"out vec3 normal;\n"
"out vec3 position;\n"
"void main()\n"
"{\n"
"normal = normalize( N * vertexNormal );\n"
"position = vec3( MV * vec4( vertexPosition, 1.0 ) );\n"
"gl_Position = MVP * vec4( vertexPosition, 1.0 );\n"
"}\n";

const char *fsrc1 =
"#version 330 core\n"
"uniform vec4 lightPosition;\n"
"uniform vec3 lightIntensity;\n"
"uniform vec3 Ka;\n"
"uniform vec3 Kd;\n"
"uniform vec3 Ks;\n"
"uniform float shininess;\n"
"in vec3 normal;\n"
"in vec3 position;\n"
"layout (location = 0) out vec4 fragColor;\n"
"vec3 adsModel(const in vec3 norm)\n"
"{\n"
"vec3 s = normalize( lightPosition.xyz - position);\n"
"vec3 v = normalize( -position.xyz );\n"
"vec3 r = reflect( -s, norm);\n"
"vec3 diffuseIntensity = vec3( max( dot( s, norm), 0.0) );\n"
"vec3 specularIntensity = vec3(0.0);\n"
"if( dot( s, norm) > 0.0 )\n"
"specularIntensity = vec3( pow( max( dot(r, v), 0.0), shininess));\n"
"return lightIntensity * (Ka +Kd * diffuseIntensity + Ks * specularIntensity);\n"
"}\n"
"void main()\n"
"{\n"
"fragColor = vec4(adsModel(normalize(normal)), 1.0);\n"
"}\n";

void ModelViewWidget::createShaderProgram()
{
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    if(!vshader->compileSourceCode(vsrc1))
        qCritical() << "Unable to compile vshader";

    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    if(!fshader->compileSourceCode(fsrc1))
        qCritical() << "Unable to compile fshader";;

    if ( !m_shaderProgram.addShader(vshader) )
        qCritical() << "Unable to add vertex shader. Log:" << m_shaderProgram.log();

    if ( !m_shaderProgram.addShader(fshader) )
        qCritical() << "Unable to add fragment shader. Log:" << m_shaderProgram.log();

    // Link the shaders together into a program
    if ( !m_shaderProgram.link() )
        qCritical() << "Unable to link shader program. Log:" << m_shaderProgram.log();

    m_shaderProgramCoordinatePlanes.addShader(vshader);
    m_shaderProgramCoordinatePlanes.addShader(fshader);
    m_shaderProgramCoordinatePlanes.link();
}

void ModelViewWidget::getCameraPosition(float &x, float &y, float &z)
{
    x = camerax;
    y = cameray;
    z = cameraz;
}
